<!DOCTYPE html>
<html lang="en">
  <head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">

  <meta name="generator" content="Hugo 0.80.0" />
  <link rel="canonical" href="/cloud/%E4%BB%8Ekubectkl%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AApod%E7%9A%84%E8%BF%87%E7%A8%8B/">

  

  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
  <link rel="manifest" href="/site.webmanifest">
  <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#000000">
  <meta name="msapplication-TileColor" content="#ffffff">
  <meta name="theme-color" content="#ffffff">

  <style>
    body {
      visibility: hidden;
      opacity: 0;
    }
  </style>

  <link rel="stylesheet" href="/cloud/css/prism.css" media="none" onload="this.media='all';">

  
  
  <link rel="stylesheet" type="text/css" href="/cloud/css/styles.css">

  
    <link rel="stylesheet" href="/cloud/css/custom_01.css">
  
    <link rel="stylesheet" href="/cloud/css/custom_02.css">
  

  
  
  <title>从kubectkl创建一个pod的过程 | 眼睫毛的博客</title>
</head>

  <body>
    <a href="#main">skip to content</a>
    <noscript>
  <style>
    body {
      visibility: visible;
      opacity: 1;
    }
  </style>
</noscript>

    <svg style="display: none">
  <symbol id="bookmark" viewBox="0 0 40 50">
   <g transform="translate(2266 3206.2)">
    <path style="stroke:currentColor;stroke-width:3.2637;fill:none" d="m-2262.2-3203.4-.2331 42.195 16.319-16.318 16.318 16.318.2331-42.428z"/>
   </g>
  </symbol>

  <symbol id="w3c" viewBox="0 0 127.09899 67.763">
   <text font-size="83" style="font-size:83px;font-family:Trebuchet;letter-spacing:-12;fill-opacity:0" letter-spacing="-12" y="67.609352" x="-26.782778">W3C</text>
   <text font-size="83" style="font-size:83px;font-weight:bold;font-family:Trebuchet;fill-opacity:0" y="67.609352" x="153.21722" font-weight="bold">SVG</text>
   <path style="fill:currentColor;image-rendering:optimizeQuality;shape-rendering:geometricPrecision" d="m33.695.377 12.062 41.016 12.067-41.016h8.731l-19.968 67.386h-.831l-12.48-41.759-12.479 41.759h-.832l-19.965-67.386h8.736l12.061 41.016 8.154-27.618-3.993-13.397h8.737z"/>
   <path style="fill:currentColor;image-rendering:optimizeQuality;shape-rendering:geometricPrecision" d="m91.355 46.132c0 6.104-1.624 11.234-4.862 15.394-3.248 4.158-7.45 6.237-12.607 6.237-3.882 0-7.263-1.238-10.148-3.702-2.885-2.47-5.02-5.812-6.406-10.022l6.82-2.829c1.001 2.552 2.317 4.562 3.953 6.028 1.636 1.469 3.56 2.207 5.781 2.207 2.329 0 4.3-1.306 5.909-3.911 1.609-2.606 2.411-5.738 2.411-9.401 0-4.049-.861-7.179-2.582-9.399-1.995-2.604-5.129-3.912-9.397-3.912h-3.327v-3.991l11.646-20.133h-14.062l-3.911 6.655h-2.493v-14.976h32.441v4.075l-12.31 21.217c4.324 1.385 7.596 3.911 9.815 7.571 2.22 3.659 3.329 7.953 3.329 12.892z"/>
   <path style="fill:currentColor;image-rendering:optimizeQuality;shape-rendering:geometricPrecision" d="m125.21 0 1.414 8.6-5.008 9.583s-1.924-4.064-5.117-6.314c-2.693-1.899-4.447-2.309-7.186-1.746-3.527.73-7.516 4.938-9.258 10.13-2.084 6.21-2.104 9.218-2.178 11.978-.115 4.428.58 7.043.58 7.043s-3.04-5.626-3.011-13.866c.018-5.882.947-11.218 3.666-16.479 2.404-4.627 5.954-7.404 9.114-7.728 3.264-.343 5.848 1.229 7.841 2.938 2.089 1.788 4.213 5.698 4.213 5.698l4.94-9.837z"/>
   <path style="fill:currentColor;image-rendering:optimizeQuality;shape-rendering:geometricPrecision" d="m125.82 48.674s-2.208 3.957-3.589 5.48c-1.379 1.524-3.849 4.209-6.896 5.555-3.049 1.343-4.646 1.598-7.661 1.306-3.01-.29-5.807-2.032-6.786-2.764-.979-.722-3.486-2.864-4.897-4.854-1.42-2-3.634-5.995-3.634-5.995s1.233 4.001 2.007 5.699c.442.977 1.81 3.965 3.749 6.572 1.805 2.425 5.315 6.604 10.652 7.545 5.336.945 9.002-1.449 9.907-2.031.907-.578 2.819-2.178 4.032-3.475 1.264-1.351 2.459-3.079 3.116-4.108.487-.758 1.276-2.286 1.276-2.286l-1.276-6.644z"/>
  </symbol>

  <symbol id="tag" viewBox="0 0 177.16535 177.16535">
    <g transform="translate(0 -875.2)">
     <path style="fill-rule:evenodd;stroke-width:0;fill:currentColor" d="m159.9 894.3-68.79 8.5872-75.42 77.336 61.931 60.397 75.429-76.565 6.8495-69.755zm-31.412 31.835a10.813 10.813 0 0 1 1.8443 2.247 10.813 10.813 0 0 1 -3.5174 14.872l-.0445.0275a10.813 10.813 0 0 1 -14.86 -3.5714 10.813 10.813 0 0 1 3.5563 -14.863 10.813 10.813 0 0 1 13.022 1.2884z"/>
    </g>
  </symbol>

  <symbol id="balloon" viewBox="0 0 141.73228 177.16535">
   <g transform="translate(0 -875.2)">
    <g>
     <path style="fill:currentColor" d="m68.156 882.83-.88753 1.4269c-4.9564 7.9666-6.3764 17.321-5.6731 37.378.36584 10.437 1.1246 23.51 1.6874 29.062.38895 3.8372 3.8278 32.454 4.6105 38.459 4.6694-.24176 9.2946.2879 14.377 1.481 1.2359-3.2937 5.2496-13.088 8.886-21.623 6.249-14.668 8.4128-21.264 10.253-31.252 1.2464-6.7626 1.6341-12.156 1.4204-19.764-.36325-12.93-2.1234-19.487-6.9377-25.843-2.0833-2.7507-6.9865-7.6112-7.9127-7.8436-.79716-.20019-6.6946-1.0922-6.7755-1.0248-.02213.0182-5.0006-.41858-7.5248-.22808l-2.149-.22808h-3.3738z"/>
     <path style="fill:currentColor" d="m61.915 883.28-3.2484.4497c-1.7863.24724-3.5182.53481-3.8494.63994-2.4751.33811-4.7267.86957-6.7777 1.5696-.28598 0-1.0254.20146-2.3695.58589-5.0418 1.4418-6.6374 2.2604-8.2567 4.2364-6.281 7.6657-11.457 18.43-12.932 26.891-1.4667 8.4111.71353 22.583 5.0764 32.996 3.8064 9.0852 13.569 25.149 22.801 37.517 1.3741 1.841 2.1708 2.9286 2.4712 3.5792 3.5437-1.1699 6.8496-1.9336 10.082-2.3263-1.3569-5.7831-4.6968-21.86-6.8361-33.002-.92884-4.8368-2.4692-14.322-3.2452-19.991-.68557-5.0083-.77707-6.9534-.74159-15.791.04316-10.803.41822-16.162 1.5026-21.503 1.4593-5.9026 3.3494-11.077 6.3247-15.852z"/>
     <path style="fill:currentColor" d="m94.499 885.78c-.10214-.0109-.13691 0-.0907.0409.16033.13489 1.329 1.0675 2.5976 2.0723 6.7003 5.307 11.273 14.568 12.658 25.638.52519 4.1949.24765 14.361-.5059 18.523-2.4775 13.684-9.7807 32.345-20.944 53.519l-3.0559 5.7971c2.8082.76579 5.7915 1.727 8.9926 2.8441 11.562-11.691 18.349-19.678 24.129-28.394 7.8992-11.913 11.132-20.234 12.24-31.518.98442-10.02-1.5579-20.876-6.7799-28.959-.2758-.4269-.57803-.86856-.89617-1.3166-3.247-6.13-9.752-12.053-21.264-16.131-2.3687-.86369-6.3657-2.0433-7.0802-2.1166z"/>
     <path style="fill:currentColor" d="m32.52 892.22c-.20090-.13016-1.4606.81389-3.9132 2.7457-11.486 9.0476-17.632 24.186-16.078 39.61.79699 7.9138 2.4066 13.505 5.9184 20.562 5.8577 11.77 14.749 23.219 30.087 38.74.05838.059.12188.1244.18052.1838 1.3166-.5556 2.5965-1.0618 3.8429-1.5199-.66408-.32448-1.4608-1.3297-3.8116-4.4602-5.0951-6.785-8.7512-11.962-13.051-18.486-5.1379-7.7948-5.0097-7.5894-8.0586-13.054-6.2097-11.13-8.2674-17.725-8.6014-27.563-.21552-6.3494.13041-9.2733 1.775-14.987 2.1832-7.5849 3.9273-10.986 9.2693-18.07 1.7839-2.3656 2.6418-3.57 2.4409-3.7003z"/>
     <path style="fill:currentColor" d="m69.133 992.37c-6.2405.0309-12.635.76718-19.554 2.5706 4.6956 4.7759 9.935 10.258 12.05 12.625l4.1272 4.6202h11.493l3.964-4.4516c2.0962-2.3541 7.4804-7.9845 12.201-12.768-8.378-1.4975-16.207-2.6353-24.281-2.5955z"/>
     <rect style="stroke-width:0;fill:currentColor" ry="2.0328" height="27.746" width="22.766" y="1017.7" x="60.201"/>
    </g>
   </g>
  </symbol>

  <symbol id="info" viewBox="0 0 41.667 41.667">
   <g transform="translate(-37.035 -1004.6)">
    <path style="stroke-linejoin:round;stroke:currentColor;stroke-linecap:round;stroke-width:3.728;fill:none" d="m76.25 1030.2a18.968 18.968 0 0 1 -23.037 13.709 18.968 18.968 0 0 1 -13.738 -23.019 18.968 18.968 0 0 1 23.001 -13.768 18.968 18.968 0 0 1 13.798 22.984"/>
    <g transform="matrix(1.1146 0 0 1.1146 -26.276 -124.92)">
     <path style="stroke:currentColor;stroke-linecap:round;stroke-width:3.728;fill:none" d="m75.491 1039.5v-8.7472"/>
     <path style="stroke-width:0;fill:currentColor" transform="scale(-1)" d="m-73.193-1024.5a2.3719 2.3719 0 0 1 -2.8807 1.7142 2.3719 2.3719 0 0 1 -1.718 -2.8785 2.3719 2.3719 0 0 1 2.8763 -1.7217 2.3719 2.3719 0 0 1 1.7254 2.8741"/>
    </g>
   </g>
  </symbol>

  <symbol id="warning" viewBox="0 0 48.430474 41.646302">
    <g transform="translate(-1.1273 -1010.2)">
     <path style="stroke-linejoin:round;stroke:currentColor;stroke-linecap:round;stroke-width:4.151;fill:none" d="m25.343 1012.3-22.14 37.496h44.28z"/>
     <path style="stroke:currentColor;stroke-linecap:round;stroke-width:4.1512;fill:none" d="m25.54 1027.7v8.7472"/>
     <path style="stroke-width:0;fill:currentColor" d="m27.839 1042.8a2.3719 2.3719 0 0 1 -2.8807 1.7143 2.3719 2.3719 0 0 1 -1.718 -2.8785 2.3719 2.3719 0 0 1 2.8763 -1.7217 2.3719 2.3719 0 0 1 1.7254 2.8741"/>
    </g>
  </symbol>

  <symbol id="menu" viewBox="0 0 50 50">
     <rect style="stroke-width:0;fill:currentColor" height="10" width="50" y="0" x="0"/>
     <rect style="stroke-width:0;fill:currentColor" height="10" width="50" y="20" x="0"/>
     <rect style="stroke-width:0;fill:currentColor" height="10" width="50" y="40" x="0"/>
   </symbol>

   <symbol id="link" viewBox="0 0 50 50">
    <g transform="translate(0 -1002.4)">
     <g transform="matrix(.095670 0 0 .095670 2.3233 1004.9)">
      <g>
       <path style="stroke-width:0;fill:currentColor" d="m452.84 192.9-128.65 128.65c-35.535 35.54-93.108 35.54-128.65 0l-42.881-42.886 42.881-42.876 42.884 42.876c11.845 11.822 31.064 11.846 42.886 0l128.64-128.64c11.816-11.831 11.816-31.066 0-42.9l-42.881-42.881c-11.822-11.814-31.064-11.814-42.887 0l-45.928 45.936c-21.292-12.531-45.491-17.905-69.449-16.291l72.501-72.526c35.535-35.521 93.136-35.521 128.64 0l42.886 42.881c35.535 35.523 35.535 93.141-.001 128.66zm-254.28 168.51-45.903 45.9c-11.845 11.846-31.064 11.817-42.881 0l-42.884-42.881c-11.845-11.821-11.845-31.041 0-42.886l128.65-128.65c11.819-11.814 31.069-11.814 42.884 0l42.886 42.886 42.876-42.886-42.876-42.881c-35.54-35.521-93.113-35.521-128.65 0l-128.65 128.64c-35.538 35.545-35.538 93.146 0 128.65l42.883 42.882c35.51 35.54 93.11 35.54 128.65 0l72.496-72.499c-23.956 1.597-48.092-3.784-69.474-16.283z"/>
      </g>
     </g>
    </g>
  </symbol>

  <symbol id="doc" viewBox="0 0 35 45">
   <g transform="translate(-147.53 -539.83)">
    <path style="stroke:currentColor;stroke-width:2.4501;fill:none" d="m149.38 542.67v39.194h31.354v-39.194z"/>
    <g style="stroke-width:25" transform="matrix(.098003 0 0 .098003 133.69 525.96)">
     <path d="m220 252.36h200" style="stroke:currentColor;stroke-width:25;fill:none"/>
     <path style="stroke:currentColor;stroke-width:25;fill:none" d="m220 409.95h200"/>
     <path d="m220 488.74h200" style="stroke:currentColor;stroke-width:25;fill:none"/>
     <path d="m220 331.15h200" style="stroke:currentColor;stroke-width:25;fill:none"/>
    </g>
   </g>
 </symbol>

 <symbol id="tick" viewBox="0 0 177.16535 177.16535">
  <g transform="translate(0 -875.2)">
   <rect style="stroke-width:0;fill:currentColor" transform="rotate(30)" height="155" width="40" y="702.99" x="556.82"/>
   <rect style="stroke-width:0;fill:currentColor" transform="rotate(30)" height="40" width="90.404" y="817.99" x="506.42"/>
  </g>
 </symbol>
</svg>

    <div class="wrapper">
      <header class="intro-and-nav" role="banner">
  <div>
    <div class="intro">
      <a
        class="logo"
        href="http://yanjiemao.gitee.io/cloud/"
        aria-label="眼睫毛的博客 home page"
      >
        <img src="/cloud/images/logo.svg" alt="">
      </a>
      <p class="library-desc">
         眼睫毛的云计算博客 
      </p>
    </div>
    <nav id="patterns-nav" class="patterns" role="navigation">
  <h2 class="vh">Main navigation</h2>
  <button id="menu-button" aria-expanded="false">
    <svg viewBox="0 0 50 50" aria-hidden="true" focusable="false">
      <use xlink:href="#menu"></use>
    </svg>
    Menu
  </button>
  
  <ul id="patterns-list">
  
    <li class="pattern">
      
      
      
      
      <a href="/cloud/" >
        <svg class="bookmark-icon" aria-hidden="true" focusable="false" viewBox="0 0 40 50">
          <use xlink:href="#bookmark"></use>
        </svg>
        <span class="text">Home</span>
      </a>
    </li>
  
    <li class="pattern">
      
      
      
      
      <a href="/cloud/post/" aria-current="page">
        <svg class="bookmark-icon" aria-hidden="true" focusable="false" viewBox="0 0 40 50">
          <use xlink:href="#bookmark"></use>
        </svg>
        <span class="text">Blog</span>
      </a>
    </li>
  
    <li class="pattern">
      
      
      
      
      <a href="/cloud/tags/" >
        <svg class="bookmark-icon" aria-hidden="true" focusable="false" viewBox="0 0 40 50">
          <use xlink:href="#bookmark"></use>
        </svg>
        <span class="text">Tags</span>
      </a>
    </li>
  
    <li class="pattern">
      
      
      
      
      <a href="/cloud/about/" >
        <svg class="bookmark-icon" aria-hidden="true" focusable="false" viewBox="0 0 40 50">
          <use xlink:href="#bookmark"></use>
        </svg>
        <span class="text">About</span>
      </a>
    </li>
  
    <li class="pattern">
      
      
      
      
      <a href="/cloud/index.xml" >
        <svg class="bookmark-icon" aria-hidden="true" focusable="false" viewBox="0 0 40 50">
          <use xlink:href="#bookmark"></use>
        </svg>
        <span class="text">RSS</span>
      </a>
    </li>
  
  </ul>
</nav>
    
  </div>
</header>

      <div class="main-and-footer">
        <div>
          
  <main id="main">
    <h1>
      <svg class="bookmark-icon" aria-hidden="true" viewBox="0 0 40 50" focusable="false">
        <use xlink:href="#bookmark"></use>
      </svg>
      从kubectkl创建一个pod的过程
    </h1>

    <div class="date">
      
      
      <strong>Publish date: </strong>Jan 22, 2021
      
        
      
    </div>

    
      <div class="tags">
        <strong>Tags: </strong>
        <ul aria-label="tags">
          
            <li>
              <svg class="tag-icon" aria-hidden="true" viewBox="0 0 177.16535 177.16535" focusable="false">
                <use xlink:href="#tag"></use>
              </svg>
              
              <a href="/cloud/tags/k8s/">k8s</a>
            </li>
          
            <li>
              <svg class="tag-icon" aria-hidden="true" viewBox="0 0 177.16535 177.16535" focusable="false">
                <use xlink:href="#tag"></use>
              </svg>
              
              <a href="/cloud/tags/pod/">pod</a>
            </li>
          
        </ul>
      </div>
    
    
    
      

  <nav class="toc" aria-labelledby="toc-heading">
    <h2 id="toc-heading">Table of contents</h2>
    <ol>
      
        <li>
          
          
          
          
          <a href="#kubectl">
            kubectl
          </a>
        </li>
      
        <li>
          
          
          
          
          <a href="#kube-apiserver">
            kube-apiserver
          </a>
        </li>
      
        <li>
          
          
          
          
          <a href="#etcd">
            etcd
          </a>
        </li>
      
        <li>
          
          
          
          
          <a href="#%e5%88%9d%e5%a7%8b%e5%8c%96">
            初始化
          </a>
        </li>
      
        <li>
          
          
          
          
          <a href="#%e6%8e%a7%e5%88%b6%e5%be%aa%e7%8e%af">
            控制循环
          </a>
        </li>
      
        <li>
          
          
          
          
          <a href="#kubelet">
            Kubelet
          </a>
        </li>
      
        <li>
          
          
          
          
          <a href="#%e4%b8%80%e5%9b%be%e7%9c%8b%e6%87%82%e8%bf%87%e7%a8%8b">
            一图看懂过程
          </a>
        </li>
      
    </ol>
  </nav>


    

    <h2 id="kubectl">kubectl</h2>
<p>通过kubectl 执行下面的命令</p>
<pre><code class="language-bash">kubectl run --image=nginx
</code></pre>
<h3 id="验证">验证</h3>
<p>首先，客户端会先验证下，确保没有简单的错误，发现简单的错误请求就会被拦截，不会发送到apiserver，这样也可以减少apiserver的压力。</p>
<p>在kubectl客户端的简单验证通过后，就会把请求进行HTTP封装然后发送到apiserver。</p>
<p>在封装http请求之前还会进行身份验证，用户的凭证保存在kubeconfig文件中，用户找到这个文件然后进行解析验证，在解析验证通过后，就会把解析得到的用户信息也一同封装在http请求中</p>
<h2 id="kube-apiserver">kube-apiserver</h2>
<h3 id="认证">认证</h3>
<p>当apiserver 收到客户端的请求后，首先会进行证书认证，如果认证失败，则请求失败并返回相应的错误信息；如果验证成功，则将请求中的 <code>Authorization</code> 请求头删除，并将用户信息其上下文中。这给后续的授权和准入控制器提供了访问之前建立的用户身份的能力。</p>
<h3 id="授权">授权</h3>
<p>在通过apiserver 的验证后，还需要进行进行授权才能进行后面的操作。</p>
<p>kube-apiserver 目前支持以下几种授权方法：</p>
<ul>
<li>webhook 它与集群外的 HTTP(S) 服务交互。</li>
<li>ABAC它执行静态文件中定义的策略。</li>
<li>RBAC 它使用 <code>rbac.authorization.k8s.io</code> API Group实现授权决策，允许管理员通过 Kubernetes API 动态配置策略。</li>
<li>Node: 它确保 kubelet 只能访问自己节点上的资源。</li>
</ul>
<h3 id="准入控制">准入控制</h3>
<p>在进行授权操作之后，还有层层的控制，不同于授权和认证只关心请求的用户和操作，准入控制还处理请求的内容，并且仅对创建、更新、删除或连接（如代理）等有效，而对读操作无效。</p>
<aside aria-label="warning" class="note warning">
  <div>
    <svg class="sign" aria-hidden="true" viewBox="0 0 48.430474 41.646302" focusable="false">
      <use xlink:href="#warning"></use>
    </svg>
    

准入控制器的工作方式与授权者和验证者的工作方式类似，但有一点区别：与验证链和授权链不同，如果某个准入控制器检查不通过，则整个链会中断，整个请求将立即被拒绝并且返回一个错误给终端用户。


  </div>
</aside>

<p>准入控制器设计的重点在于提高可扩展性，某个控制器都作为一个插件存储在 <code>plugin/pkg/admission</code> 目录中，并且与某一个接口相匹配，最后被编译到 kube-apiserver 二进制文件中。</p>
<p>Kubernetes 目前支持的准入控制器有：</p>
<div class="expandable-section">
  
    <h2>
  
    
    
    
    
    
    <button aria-expanded="false" data-expands="js-expandable-2879650519e350359f4398aa53b46ce0">
      <span class="expandable-label">控制器列表</span>
      <svg aria-hidden="true" focusable="false" viewBox="0 0 70.866142 70.866141">
        <g transform="translate(0 -981.5)">
          <rect style="stroke-width:0;fill:currentColor" ry="5" height="60" width="9.8985" y="987.36" x="30.051" class="up-strut" />
          <rect style="stroke-width:0;fill:currentColor" ry="5" height="10" width="60" y="1012.4" x="5"/>
        </g>
      </svg>
    </button>
  
    </h2>
  
  <div id="js-expandable-2879650519e350359f4398aa53b46ce0" hidden>
    <ul>
<li>AlwaysPullImages：此准入控制器修改每个 Pod 的时候都强制重新拉取镜像。</li>
<li>DefaultStorageClass：此准入控制器观察创建<code>PersistentVolumeClaim</code>时不请求任何特定存储类的对象，并自动向其添加默认存储类。这样，用户就不需要关注特殊存储类而获得默认存储类。</li>
<li>DefaultTolerationSeconds：此准入控制器将Pod的容忍时间<code>notready:NoExecute</code>和<code>unreachable:NoExecute</code> 默认设置为5分钟。</li>
<li>DenyEscalatingExec：此准入控制器将拒绝<code>exec</code> 和附加命令到以允许访问宿主机的升级了权限运行的pod。</li>
<li>EventRateLimit (alpha)：此准入控制器缓解了 API Server 被事件请求淹没的问题，限制时间速率。</li>
<li>ExtendedResourceToleration：此插件有助于创建具有扩展资源的专用节点。</li>
<li>ImagePolicyWebhook：此准入控制器允许后端判断镜像拉取策略，例如配置镜像仓库的密钥。</li>
<li>Initializers (alpha)：Pod初始化的准入控制器，详情请参考<a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/">动态准入控制</a>。</li>
<li>LimitPodHardAntiAffinityTopology：此准入控制器拒绝任何在 <code>requiredDuringSchedulingRequiredDuringExecution</code> 的 <code>AntiAffinity</code> 字段中定义除了<code>kubernetes.io/hostname</code> 之外的拓扑关键字的 pod 。</li>
<li>LimitRanger：此准入控制器将确保所有资源请求不会超过 namespace 的 <code>LimitRange</code>。</li>
<li>MutatingAdmissionWebhook （1.9版本中为beta）：该准入控制器调用与请求匹配的任何变更 webhook。匹配的 webhook是串行调用的；如果需要，每个人都可以修改对象。</li>
<li>NamespaceAutoProvision：此准入控制器检查命名空间资源上的所有传入请求，并检查引用的命名空间是否存在。如果不存在就创建一个命名空间。</li>
<li>NamespaceExists：此许可控制器检查除 <code>Namespace</code> 其自身之外的命名空间资源上的所有请求。如果请求引用的命名空间不存在，则拒绝该请求。</li>
<li>NamespaceLifecycle：此准入控制器强制执行正在终止的命令空间中不能创建新对象，并确保<code>Namespace</code>拒绝不存在的请求。此准入控制器还防止缺失三个系统保留的命名空间<code>default</code>、<code>kube-system</code>、<code>kube-public</code>。</li>
<li>NodeRestriction：该准入控制器限制了 kubelet 可以修改的<code>Node</code>和<code>Pod</code>对象。</li>
<li>OwnerReferencesPermissionEnforcement：此准入控制器保护对<code>metadata.ownerReferences</code>对象的访问，以便只有对该对象具有“删除”权限的用户才能对其进行更改。</li>
<li>PodNodeSelector：此准入控制器通过读取命名空间注释和全局配置来限制可在命名空间内使用的节点选择器。</li>
<li>PodPreset：此准入控制器注入一个pod，其中包含匹配的PodPreset中指定的字段，详细信息见<a href="https://jimmysong.io/kubernetes-handbook/concepts/pod-preset.html">Pod Preset</a>。</li>
<li>PodSecurityPolicy：此准入控制器用于创建和修改pod，并根据请求的安全上下文和可用的Pod安全策略确定是否应该允许它。</li>
<li>PodTolerationRestriction：此准入控制器首先验证容器的容忍度与其命名空间的容忍度之间是否存在冲突，并在存在冲突时拒绝该容器请求。</li>
<li>Priority：此控制器使用<code>priorityClassName</code>字段并填充优先级的整数值。如果未找到优先级，则拒绝Pod。</li>
<li>ResourceQuota：此准入控制器将观察传入请求并确保它不违反命名空间的<code>ResourceQuota</code>对象中列举的任何约束。</li>
<li>SecurityContextDeny：此准入控制器将拒绝任何试图设置某些升级的<a href="https://kubernetes.io/docs/user-guide/security-context">SecurityContext</a>字段的pod 。</li>
<li>ServiceAccount：此准入控制器实现<a href="https://kubernetes.io/docs/user-guide/service-accounts">serviceAccounts的</a>自动化。</li>
</ul>

  </div>
</div>

<h2 id="etcd">etcd</h2>
<p>到现在为止，Kubernetes 已经对该客户端的调用请求进行了全面彻底地审查，并且已经验证通过，运行它进入下一个环节。下一步 kube-apiserver 将对 HTTP 请求进行反序列化，然后利用得到的结果构建运行时对象（有点像 kubectl 生成器的逆过程），并保存到 <code>etcd</code> 中。下面我们将这个过程分解一下。</p>
<p>当收到请求时，kube-apiserver 是如何知道它该怎么做的呢？事实上，在客户端发送调用请求之前就已经产生了一系列非常复杂的流程。我们就从 kube-apiserver 二进制文件首次运行开始分析吧：</p>
<ol>
<li>当运行 kube-apiserver 二进制文件时，它会<a href="https://github.com/kubernetes/kubernetes/blob/7650665059e65b4b22375d1e28da5306536a12fb/cmd/kube-apiserver/app/server.go#L119">创建一个允许 apiserver 聚合的服务链</a>。这是一种对 <code>Kubernetes API</code> 进行扩展的方式。</li>
<li>同时会创建一个 <code>generic apiserver</code> 作为默认的 apiserver。</li>
<li>然后利用<a href="https://github.com/kubernetes/apiserver/blob/7001bc4df8883d4a0ec84cd4b2117655a0009b6c/pkg/server/config.go#L149">生成的 OpenAPI 规范</a>来填充 apiserver 的配置。</li>
<li>然后 kube-apiserver 遍历数据结构中指定的所有 API 组，并将每一个 API 组作为通用的存储抽象保存到 etcd 中。当你访问或变更资源状态时，kube-apiserver 就会调用这些 API 组。</li>
<li>每个 API 组都会遍历它的所有组版本，并且将每个 HTTP 路由<a href="https://github.com/kubernetes/apiserver/blob/7001bc4df8883d4a0ec84cd4b2117655a0009b6c/pkg/endpoints/groupversion.go#L92">映射到 REST 路径中</a>。</li>
<li>当请求的 METHOD 是 <code>POST</code> 时，kube-apiserver 就会将请求转交给 <a href="https://github.com/kubernetes/apiserver/blob/7001bc4df8883d4a0ec84cd4b2117655a0009b6c/pkg/endpoints/handlers/create.go#L37">资源创建处理器</a>。</li>
</ol>
<p>现在 kube-apiserver 已经知道了所有的路由及其对应的 REST 路径，以便在请求匹配时知道调用哪些处理器和键值存储。多么机智的设计！现在假设客户端的 HTTP 请求已经被 kube-apiserver 收到了：</p>
<ol>
<li>如果处理链可以将请求与已经注册的路由进行匹配，就会将该请求交给注册到该路由的<a href="https://github.com/kubernetes/apiserver/blob/7001bc4df8883d4a0ec84cd4b2117655a0009b6c/pkg/server/handler.go#L143">专用处理器</a>来处理；如果没有任何一个路由可以匹配该请求，就会将请求转交给<a href="https://github.com/kubernetes/apiserver/blob/7001bc4df8883d4a0ec84cd4b2117655a0009b6c/pkg/server/mux/pathrecorder.go#L248">基于路径的处理器</a>（比如当调用 <code>/apis</code> 时）；如果没有任何一个基于路径的处理器注册到该路径，请求就会被转交给 <a href="https://github.com/kubernetes/apiserver/blob/7001bc4df8883d4a0ec84cd4b2117655a0009b6c/pkg/server/mux/pathrecorder.go#L254">not found 处理器</a>，最后返回 <code>404</code>。</li>
<li>幸运的是，我们有一个名为 <code>createHandler</code> 的注册路由！它有什么作用呢？首先它会解码 HTTP 请求并进行基本的验证，例如确保请求提供的 json 与 API 资源的版本相匹配。</li>
<li>接下来进入<a href="https://github.com/kubernetes/apiserver/blob/7001bc4df8883d4a0ec84cd4b2117655a0009b6c/pkg/endpoints/handlers/create.go#L93-L104">审计和准入控制</a>阶段。</li>
<li>然后资源将会通过 <a href="https://github.com/kubernetes/apiserver/blob/19667a1afc13cc13930c40a20f2c12bbdcaaa246/pkg/registry/generic/registry/store.go#L327">storage provider</a> 保存<a href="https://github.com/kubernetes/apiserver/blob/7001bc4df8883d4a0ec84cd4b2117655a0009b6c/pkg/endpoints/handlers/create.go#L111">到 etcd</a> 中。默认情况下保存到 etcd 中的键的格式为 <code>&lt;namespace&gt;/&lt;name&gt;</code>，你也可以自定义。</li>
<li>资源创建过程中出现的任何错误都会被捕获，最后 <code>storage provider</code> 会执行 <code>get</code> 调用来确认该资源是否被成功创建。如果需要额外的清理工作，就会调用后期创建的处理器和装饰器。</li>
<li>最后构造 HTTP 响应并返回给客户端。</li>
</ol>
<p>原来 apiserver 做了这么多的工作，以前竟然没有发现呢！到目前为止，我们创建的 <code>Deployment</code> 资源已经保存到了 etcd 中，但 apiserver 仍然看不到它。</p>
<h2 id="初始化">初始化</h2>
<hr>
<p>在一个资源对象被持久化到数据存储之后，apiserver 还无法完全看到或调度它，在此之前还要执行一系列<a href="https://v1-13.docs.kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#initializers">Initializers</a>。Initializers是一种与资源类型相关联的控制器，它会在资源对外可用之前执行某些逻辑。如果某个资源类型没有Initializers，就会跳过此初始化步骤立即使资源对外可见。</p>
<p>正如<a href="https://ahmet.im/blog/initializers/">大佬的博客</a>指出的那样，Initializers是一个强大的功能，因为它允许我们执行通用引导操作。例如：</p>
<ul>
<li>将代理边车容器注入到暴露 80 端口的 Pod 中，或者加上特定的 <code>annotation</code>。</li>
<li>将保存着测试证书的 <code>volume</code> 注入到特定命名空间的所有 Pod 中。</li>
<li>如果 <code>Secret</code> 中的密码小于 20 个字符，就组织其创建。</li>
</ul>
<p><code>initializerConfiguration</code> 资源对象允许你声明某些资源类型应该运行哪些Initializers。如果你想每创建一个 Pod 时就运行一个自定义Initializers，你可以这样做：</p>
<pre><code class="language-yaml">apiVersion: admissionregistration.k8s.io/v1alpha1
kind: InitializerConfiguration
metadata:
  name: custom-pod-initializer
initializers:
  - name: podimage.example.com
    rules:
      - apiGroups:
          - &quot;&quot;
        apiVersions:
          - v1
        resources:
          - pods
</code></pre>
<p>通过该配置创建资源对象 <code>InitializerConfiguration</code> 之后，就会在每个 Pod 的 <code>metadata.initializers.pending</code> 字段中添加 <code>custom-pod-initializer</code> 字段。该初始化控制器会定期扫描新的 Pod，一旦在 Pod 的 <code>pending</code> 字段中检测到自己的名称，就会执行其逻辑，执行完逻辑之后就会将 <code>pending</code> 字段下的自己的名称删除。</p>
<p>只有在 <code>pending</code> 字段下的列表中的第一个Initializers可以对资源进行操作，当所有的Initializers执行完成，并且 <code>pending</code> 字段为空时，该对象就会被认为初始化成功。</p>
<p><strong>你可能会注意到一个问题：如果 kube-apiserver 不能显示这些资源，那么用户级控制器是如何处理资源的呢？</strong></p>
<p>为了解决这个问题，kube-apiserver 暴露了一个 <code>?includeUninitialized</code> 查询参数，它会返回所有的资源对象（包括未初始化的）。</p>
<h2 id="控制循环">控制循环</h2>
<hr>
<h3 id="deployments-controller">Deployments controller</h3>
<p>到了这个阶段，我们的 Deployment 记录已经保存在 etcd 中，并且所有的初始化逻辑都执行完成，接下来的阶段将会涉及到该资源所依赖的拓扑结构。在 Kubernetes 中，Deployment 实际上只是一系列 <code>Replicaset</code> 的集合，而 Replicaset 是一系列 <code>Pod</code> 的集合。那么 Kubernetes 是如何从一个 HTTP 请求按照层级结构依次创建这些资源的呢？其实这些工作都是由 Kubernetes 内置的 <code>Controller</code>(控制器) 来完成的。</p>
<p>Kubernetes 在整个系统中使用了大量的 Controller，Controller 是一个用于将系统状态从“当前状态”修正到“期望状态”的异步脚本。所有 Controller 都通过 <code>kube-controller-manager</code> 组件并行运行，每种 Controller 都负责一种具体的控制流程。首先介绍一下 <code>Deployment Controller</code>：</p>
<p>将 Deployment 记录存储到 etcd 并初始化后，就可以通过 kube-apiserver 使其可见，然后 <code>Deployment Controller</code> 就会检测到它（它的工作就是负责监听 Deployment 记录的更改）。在我们的例子中，控制器通过一个 <code>Informer</code> <a href="https://github.com/kubernetes/kubernetes/blob/7650665059e65b4b22375d1e28da5306536a12fb/pkg/controller/deployment/deployment_controller.go#L122">注册一个创建事件的特定回调函数</a>（更多信息参加下文）。</p>
<p>当 Deployment 第一次对外可见时，该 Controller 就会<a href="https://github.com/kubernetes/kubernetes/blob/7650665059e65b4b22375d1e28da5306536a12fb/pkg/controller/deployment/deployment_controller.go#L170">将该资源对象添加到内部工作队列</a>，然后开始处理这个资源对象：</p>
<blockquote>
<p>通过使用标签选择器查询 kube-apiserver 来<a href="https://github.com/kubernetes/kubernetes/blob/7650665059e65b4b22375d1e28da5306536a12fb/pkg/controller/deployment/deployment_controller.go#L633">检查</a>该 Deployment 是否有与其关联的 <code>ReplicaSet</code> 或 <code>Pod</code> 记录。</p>
</blockquote>
<p>有趣的是，这个同步过程是状态不可知的，它核对新记录与核对已经存在的记录采用的是相同的方式。</p>
<p>在意识到没有与其关联的 <code>ReplicaSet</code> 或 <code>Pod</code> 记录后，Deployment Controller 就会开始执行<a href="https://github.com/kubernetes/kubernetes/blob/7650665059e65b4b22375d1e28da5306536a12fb/pkg/controller/deployment/sync.go#L385">弹性伸缩流程</a>：</p>
<blockquote>
<p>创建 ReplicaSet 资源，为其分配一个标签选择器并将其版本号设置为 1。</p>
</blockquote>
<p>ReplicaSet 的 <code>PodSpec</code> 字段从 Deployment 的 manifest 以及其他相关元数据中复制而来。有时 Deployment 记录在此之后也需要更新（例如，如果设置了 <code>process deadline</code>）。</p>
<p>当完成以上步骤之后，该 Deployment 的 <code>status</code> 就会被更新，然后重新进入与之前相同的循环，等待 Deployment 与期望的状态相匹配。由于 Deployment Controller 只关心 ReplicaSet，因此需要通过 <code>ReplicaSet Controller</code> 来继续协调。</p>
<h3 id="replicasets-controller">ReplicaSets controller</h3>
<p>在前面的步骤中，Deployment Controller 创建了第一个 ReplicaSet，但仍然还是没有 Pod，这时候就该 <code>ReplicaSet Controller</code> 登场了！ReplicaSet Controller 的工作是监视 ReplicaSets 及其相关资源（Pod）的生命周期。和大多数其他 Controller 一样，它通过触发某些事件的处理器来实现此目的。</p>
<p>当创建 ReplicaSet 时（由 Deployment Controller 创建），RS Controller <a href="https://github.com/kubernetes/kubernetes/blob/7650665059e65b4b22375d1e28da5306536a12fb/pkg/controller/replicaset/replica_set.go#L583">检查新 ReplicaSet 的状态</a>，并检查当前状态与期望状态之间存在的偏差，然后通过<a href="https://github.com/kubernetes/kubernetes/blob/7650665059e65b4b22375d1e28da5306536a12fb/pkg/controller/replicaset/replica_set.go#L460">调整 Pod 的副本数</a>来达到期望的状态。</p>
<p>Pod 的创建也是批量进行的，从 <code>SlowStartInitialBatchSize</code> 开始，然后在每次成功的迭代中以一种 <code>slow start</code> 操作加倍。这样做的目的是在大量 Pod 启动失败时（例如，由于资源配额），可以减轻 kube-apiserver 被大量不必要的 HTTP 请求吞没的风险。如果创建失败，最好能够优雅地失败，并且对其他的系统组件造成的影响最小！</p>
<p>Kubernetes 通过 <code>Owner References</code>（在子级资源的某个字段中引用其父级资源的 ID） 来构造严格的资源对象层级结构。这确保了一旦 Controller 管理的资源被删除（级联删除），子资源就会被垃圾收集器删除，同时还为父级资源提供了一种有效的方式来避免他们竞争同一个子级资源（想象两对父母都认为他们拥有同一个孩子的场景）。</p>
<p>Owner References 的另一个好处是：它是有状态的。如果有任何 Controller 重启了，那么由于资源对象的拓扑关系与 Controller 无关，该操作不会影响到系统的稳定运行。这种对资源隔离的重视也体现在 Controller 本身的设计中：Controller 不能对自己没有明确拥有的资源进行操作，它们应该选择对资源的所有权，互不干涉，互不共享。</p>
<p>有时系统中也会出现孤儿（orphaned）资源，通常由以下两种途径产生：</p>
<ul>
<li>父级资源被删除，但子级资源没有被删除</li>
<li>垃圾收集策略禁止删除子级资源</li>
</ul>
<p>当发生这种情况时，Controller 将会确保孤儿资源拥有新的 <code>Owner</code>。多个父级资源可以相互竞争同一个孤儿资源，但只有一个会成功（其他父级资源会收到验证错误）。</p>
<h3 id="informers">Informers</h3>
<p>你可能已经注意到，某些 Controller（例如 RBAC 授权器或 Deployment Controller）需要先检索集群状态然后才能正常运行。拿 RBAC 授权器举例，当请求进入时，授权器会将用户的初始状态缓存下来，然后用它来检索与 etcd 中的用户关联的所有 角色（<code>Role</code>）和 角色绑定（<code>RoleBinding</code>）。那么问题来了，Controller 是如何访问和修改这些资源对象的呢？事实上 Kubernetes 是通过 <code>Informer</code> 机制来解决这个问题的。</p>
<p>Infomer 是一种模式，它允许 Controller 查找缓存在本地内存中的数据(这份数据由 Informer 自己维护)并列出它们感兴趣的资源。</p>
<p>虽然 Informer 的设计很抽象，但它在内部实现了大量的对细节的处理逻辑（例如缓存），缓存很重要，因为它不但可以减少对 Kubenetes API 的直接调用，同时也能减少 Server 和 Controller 的大量重复性工作。通过使用 Informer，不同的 Controller 之间以线程安全（Thread safety）的方式进行交互，而不必担心多个线程访问相同的资源时会产生冲突。</p>
<p>有关 Informer 的更多详细解析，请参考这篇文章：<a href="https://borismattijssen.github.io/articles/kubernetes-informers-controllers-reflectors-stores">Kubernetes: Controllers, Informers, Reflectors and Stores</a></p>
<h3 id="scheduler">Scheduler</h3>
<p>当所有的 Controller 正常运行后，etcd 中就会保存一个 Deployment、一个 ReplicaSet 和 三个 Pod 资源记录，并且可以通过 kube-apiserver 查看。然而，这些 Pod 资源现在还处于 <code>Pending</code> 状态，因为它们还没有被调度到集群中合适的 Node 上运行。这个问题最终要靠调度器（Scheduler）来解决。</p>
<p><code>Scheduler</code> 作为一个独立的组件运行在集群控制平面上，工作方式与其他 Controller 相同：监听实际并将系统状态调整到期望的状态。具体来说，Scheduler 的作用是将待调度的 Pod 按照特定的算法和调度策略绑定（Binding）到集群中某个合适的 Node 上，并将绑定信息写入 etcd 中（它会过滤其 PodSpec 中 <code>NodeName</code> 字段为空的 Pod），默认的调度算法的工作方式如下：</p>
<ol>
<li>当 Scheduler 启动时，会<a href="https://github.com/kubernetes/kubernetes/blob/2d64ce5e8e45e26b02492d2b6c85e5ebfb1e4761/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go#L65-L81">注册一个默认的预选策略链</a>，这些 <code>预选策略</code> 会对备选节点进行评估，判断备选节点是否<a href="https://github.com/kubernetes/kubernetes/blob/2d64ce5e8e45e26b02492d2b6c85e5ebfb1e4761/plugin/pkg/scheduler/core/generic_scheduler.go#L117">满足备选 Pod 的需求</a>。例如，如果 PodSpec 字段限制了 CPU 和内存资源，那么当备选节点的资源容量不满足备选 Pod 的需求时，备选 Pod 就不会被调度到该节点上（<strong>资源容量=备选节点资源总量-节点中已存在 Pod 的所有容器的需求资源（CPU 和内存）的总和</strong>）</li>
<li>一旦筛选出符合要求的候选节点，就会采用 <code>优选策略</code> 计算出每个候选节点的积分，然后对这些候选节点进行排序，积分最高者胜出。例如，为了在整个系统中分摊工作负载，这些优选策略会从备选节点列表中选出资源消耗最小的节点。每个节点通过优选策略时都会算出一个得分，计算各项得分，最终选出分值大的节点作为优选的结果。</li>
</ol>
<p>一旦找到了合适的节点，Scheduler 就会创建一个 <code>Binding</code> 对象，该对象的 <code>Name</code> 和 <code>Uid</code> 与 Pod 相匹配，并且其 <code>ObjectReference</code> 字段包含所选节点的名称，然后通过 <code>POST</code> 请求<a href="https://github.com/kubernetes/kubernetes/blob/2d64ce5e8e45e26b02492d2b6c85e5ebfb1e4761/plugin/pkg/scheduler/factory/factory.go#L1095">发送给 apiserver</a>。</p>
<p>当 kube-apiserver 接收到此 Binding 对象时，注册吧会将该对象<strong>反序列化</strong>并更新 Pod 资源中的以下字段：</p>
<ul>
<li>将 <code>NodeName</code> 的值设置为 ObjectReference 中的 NodeName。</li>
<li>添加相关的注释。</li>
<li>将 <code>PodScheduled</code> 的 <code>status</code> 值设置为 True。可以通过 kubectl 来查看：</li>
</ul>
<pre><code class="language-bash">$ kubectl get &lt;PODNAME&gt; -o go-template='{{range .status.conditions}}{{if eq .type &quot;PodScheduled&quot;}}{{.status}}{{end}}{{end}}'
</code></pre>
<p>一旦 Scheduler 将 Pod 调度到某个节点上，该节点的 <code>Kubelet</code> 就会接管该 Pod 并开始部署。</p>
<p>预选策略和优选策略都可以通过 <code>–policy-config-file</code> 参数来扩展，如果默认的调度器不满足要求，还可以部署自定义的调度器。如果 <code>podSpec.schedulerName</code> 的值设置为其他的调度器，则 Kubernetes 会将该 Pod 的调度转交给那个调度器。</p>
<h2 id="kubelet">Kubelet</h2>
<hr>
<h3 id="pod-同步">Pod 同步</h3>
<p>现在，所有的 Controller 都完成了工作，我们来总结一下：</p>
<ul>
<li>HTTP 请求通过了认证、授权和准入控制阶段。</li>
<li>一个 Deployment、ReplicaSet 和三个 Pod 资源被持久化到 etcd 存储中。</li>
<li>然后运行了一系列Initializers。</li>
<li>最后每个 Pod 都被调度到合适的节点。</li>
</ul>
<p>然而到目前为止，所有的状态变化仅仅只是针对保存在 etcd 中的资源记录，接下来的步骤涉及到运行在工作节点之间的 Pod 的分布状况，这是分布式系统（比如 Kubernetes）的关键因素。这些任务都是由 <code>Kubelet</code> 组件完成的，让我们开始吧！</p>
<p>在 Kubernetes 集群中，每个 Node 节点上都会启动一个 Kubelet 服务进程，该进程用于处理 Scheduler 下发到本节点的任务，管理 Pod 的生命周期，包括挂载卷、容器日志记录、垃圾回收以及其他与 Pod 相关的事件。</p>
<p>如果换一种思维模式，你可以把 Kubelet 当成一种特殊的 Controller，它每隔 20 秒（可以自定义）向 kube-apiserver 通过 <code>NodeName</code> 获取自身 Node 上所要运行的 Pod 清单。一旦获取到了这个清单，它就会通过与自己的内部缓存进行比较来检测新增加的 Pod，如果有差异，就开始同步 Pod 列表。我们来详细分析一下同步过程：</p>
<ol>
<li>如果 Pod 正在创建， Kubelet 就会<a href="https://github.com/kubernetes/kubernetes/blob/fc8bfe2d8929e11a898c4557f9323c482b5e8842/pkg/kubelet/kubelet.go#L1519">记录一些在 <code>Prometheus</code> 中用于追踪 Pod 启动延时的指标</a>。</li>
<li>然后生成一个 <code>PodStatus</code> 对象，它表示 Pod 当前阶段的状态。Pod 的状态(<code>Phase</code>) 是 Pod 在其生命周期中的最精简的概要，包括 <code>Pending</code>，<code>Running</code>，<code>Succeeded</code>，<code>Failed</code> 和 <code>Unkown</code> 这几个值。状态的产生过程非常过程，所以很有必要深入了解一下背后的原理：</li>
</ol>
<ul>
<li>
<p>首先串行执行一系列 Pod 同步处理器（<code>PodSyncHandlers</code>），每个处理器检查检查 Pod 是否应该运行在该节点上。当所有的处理器都认为该 Pod 不应该运行在该节点上，则 Pod 的 <code>Phase</code> 值就会变成 <code>PodFailed</code>，并且将该 Pod 从该节点上驱逐出去。例如当你创建一个 <code>Job</code> 时，如果 Pod 失败重试的时间超过了 <code>spec.activeDeadlineSeconds</code> 设置的值，就会将 Pod 从该节点驱逐出去。</p>
</li>
<li>
<p>接下来，Pod 的 Phase 值由 <code>init 容器</code> 和应用容器的状态共同来决定。因为目前容器还没有启动，容器被视为<a href="https://github.com/kubernetes/kubernetes/blob/fc8bfe2d8929e11a898c4557f9323c482b5e8842/pkg/kubelet/kubelet_pods.go#L1244">处于等待阶段</a>，如果 Pod 中至少有一个容器处于等待阶段，则其 <code>Phase</code> 值为 <a href="https://github.com/kubernetes/kubernetes/blob/fc8bfe2d8929e11a898c4557f9323c482b5e8842/pkg/kubelet/kubelet_pods.go#L1258-L1261">Pending</a>。</p>
</li>
<li>
<p>最后，Pod 的 <code>Condition</code> 字段由 Pod 内所有容器的状态决定。现在我们的容器还没有被容器运行时创建，所以 <a href="https://github.com/kubernetes/kubernetes/blob/fc8bfe2d8929e11a898c4557f9323c482b5e8842/pkg/kubelet/status/generate.go#L70-L81"><code>PodReady</code> 的状态被设置为 <code>False</code></a>。可以通过 kubectl 查看：</p>
<pre><code class="language-bash">$ kubectl get &lt;PODNAME&gt; -o go-template='{{range .status.conditions}}{{if eq .type &quot;Ready&quot;}}{{.status}}{{end}}{{end}}'
</code></pre>
</li>
</ul>
<ol>
<li>生成 PodStatus 之后（Pod 中的 <code>status</code> 字段），Kubelet 就会将它发送到 Pod 的状态管理器，该管理器的任务是通过 apiserver 异步更新 etcd 中的记录。</li>
<li>接下来运行一系列<strong>准入处理器</strong>来确保该 Pod 是否具有相应的权限（包括强制执行 <a href="https://github.com/kubernetes/kubernetes/blob/fc8bfe2d8929e11a898c4557f9323c482b5e8842/pkg/kubelet/kubelet.go#L883-L884"><code>AppArmor</code> 配置文件和 <code>NO_NEW_PRIVS</code></a>），被准入控制器拒绝的 Pod 将一直保持 <code>Pending</code> 状态。</li>
<li>如果 Kubelet 启动时指定了 <code>cgroups-per-qos</code> 参数，Kubelet 就会为该 Pod 创建 <code>cgroup</code> 并进行相应的资源限制。这是为了更方便地对 Pod 进行服务质量（QoS）管理。</li>
<li>然后为 Pod 创建相应的目录，包括 Pod 的目录（<code>/var/run/kubelet/pods/&lt;podID&gt;</code>），该 Pod 的卷目录（<code>&lt;podDir&gt;/volumes</code>）和该 Pod 的插件目录（<code>&lt;podDir&gt;/plugins</code>）。</li>
<li><strong>卷管理器</strong>会<a href="https://github.com/kubernetes/kubernetes/blob/2723e06a251a4ec3ef241397217e73fa782b0b98/pkg/kubelet/volumemanager/volume_manager.go#L330">挂载 <code>Spec.Volumes</code> 中定义的相关数据卷，然后等待是否挂载成功</a>。根据挂载卷类型的不同，某些 Pod 可能需要等待更长的时间（比如 NFS 卷）。</li>
<li><a href="https://github.com/kubernetes/kubernetes/blob/dd9981d038012c120525c9e6df98b3beb3ef19e1/pkg/kubelet/kubelet_pods.go#L788">从 apiserver 中检索</a> <code>Spec.ImagePullSecrets</code> 中定义的所有 <code>Secret</code>，然后将其注入到容器中。</li>
<li>最后通过容器运行时接口（<code>Container Runtime Interface（CRI）</code>）开始启动容器（下面会详细描述）。</li>
</ol>
<h3 id="cri-与-pause-容器">CRI 与 pause 容器</h3>
<p>到了这个阶段，大量的初始化工作都已经完成，容器已经准备好开始启动了，而容器是由<strong>容器运行时</strong>（例如 <code>Docker</code> 和 <code>Rkt</code>）启动的。</p>
<p>为了更容易扩展，Kubelet 从 1.5.0 开始通过<strong>容器运行时接口</strong>与容器运行时（Container Runtime）交互。简而言之，CRI 提供了 Kubelet 和特定的运行时之间的抽象接口，它们之间通过<a href="https://github.com/google/protobuf">协议缓冲区</a>（它像一个更快的 JSON）和 <a href="https://grpc.io/">gRPC API</a>（一种非常适合执行 Kubernetes 操作的 API）。这是一个非常酷的想法，通过使用 Kubelet 和运行时之间定义的契约关系，容器如何编排的具体实现细节已经变得无关紧要。由于不需要修改 Kubernetes 的核心代码，开发者可以以最小的开销添加新的运行时。</p>
<p>不好意思有点跑题了，让我们继续回到容器启动的阶段。第一次启动 Pod 时，Kubelet 会通过 <code>Remote Procedure Command</code>(RPC) 协议调用 <a href="https://github.com/kubernetes/kubernetes/blob/2d64ce5e8e45e26b02492d2b6c85e5ebfb1e4761/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go#L51">RunPodSandbox</a>。<code>sandbox</code> 用于描述一组容器，例如在 Kubernetes 中它表示的是 Pod。<code>sandbox</code> 是一个很宽泛的概念，所以对于其他没有使用容器的运行时仍然是有意义的（比如在一个基于 <code>hypervisor</code> 的运行时中，sandbox 可能指的就是虚拟机）。</p>
<p>我们的例子中使用的容器运行时是 Docker，创建 sandbox 时首先创建的是 <code>pause</code> 容器。pause 容器作为同一个 Pod 中所有其他容器的基础容器，它为 Pod 中的每个业务容器提供了大量的 Pod 级别资源，这些资源都是 Linux 命名空间（包括网络命名空间，IPC 命名空间和 PID 命名空间）。</p>
<p>pause 容器提供了一种方法来管理所有这些命名空间并允许业务容器共享它们，在同一个网络命名空间中的好处是：同一个 Pod 中的容器可以使用 <code>localhost</code> 来相互通信。pause 容器的第二个功能与 PID 命名空间的工作方式相关，在 PID 命名空间中，进程之间形成一个树状结构，一旦某个子进程由于父进程的错误而变成了“孤儿进程”，其便会被 <code>init</code> 进程进行收养并最终回收资源。关于 pause 工作方式的详细信息可以参考：<a href="https://www.ianlewis.org/en/almighty-pause-container">The Almighty Pause Container</a>。</p>
<p>一旦创建好了 pause 容器，下面就会开始检查磁盘状态然后开始启动业务容器。</p>
<h3 id="cni-和-pod-网络">CNI 和 Pod 网络</h3>
<p>现在我们的 Pod 已经有了基本的骨架：一个共享所有命名空间以允许业务容器在同一个 Pod 里进行通信的 pause 容器。但现在还有一个问题，那就是容器的网络是如何建立的？</p>
<p>当 Kubelet 为 Pod 创建网络时，它会将创建网络的任务交给 <code>CNI</code> 插件。CNI 表示容器网络接口（Container Network Interface），和容器运行时的运行方式类似，它也是一种抽象，允许不同的网络提供商为容器提供不同的网络实现。通过将 json 配置文件（默认在 <code>/etc/cni/net.d</code> 路径下）中的数据传送到相关的 CNI 二进制文件（默认在 <code>/opt/cni/bin</code> 路径下）中，cni 插件可以给 pause 容器配置相关的网络，然后 Pod 中其他的容器都使用 pause 容器的网络。下面是一个简单的示例配置文件：</p>
<pre><code class="language-json">{
    &quot;cniVersion&quot;: &quot;0.3.1&quot;,
    &quot;name&quot;: &quot;bridge&quot;,
    &quot;type&quot;: &quot;bridge&quot;,
    &quot;bridge&quot;: &quot;cnio0&quot;,
    &quot;isGateway&quot;: true,
    &quot;ipMasq&quot;: true,
    &quot;ipam&quot;: {
        &quot;type&quot;: &quot;host-local&quot;,
        &quot;ranges&quot;: [
          [{&quot;subnet&quot;: &quot;${POD_CIDR}&quot;}]
        ],
        &quot;routes&quot;: [{&quot;dst&quot;: &quot;0.0.0.0/0&quot;}]
    }
}
</code></pre>
<p>CNI 插件还会通过 <code>CNI_ARGS</code> 环境变量为 Pod 指定其他的元数据，包括 Pod 名称和命名空间。</p>
<p>下面的步骤因 CNI 插件而异，我们以 <code>bridge</code> 插件举例：</p>
<ul>
<li>该插件首先会在根网络命名空间（也就是宿主机的网络命名空间）中设置本地 Linux 网桥，以便为该主机上的所有容器提供网络服务。</li>
<li>然后它会将一个网络接口（<code>veth</code> 设备对的一端）插入到 pause 容器的网络命名空间中，并将另一端连接到网桥上。你可以这样来理解 veth 设备对：它就像一根很长的管道，一端连接到容器，一端连接到根网络命名空间中，数据包就在管道中进行传播。</li>
<li>接下来 json 文件中指定的 <code>IPAM</code> Plugin 会为 pause 容器的网络接口分配一个 IP 并设置相应的路由，现在 Pod 就有了自己的 IP。
<ul>
<li>IPAM Plugin 的工作方式和 CNI Plugin 类似：通过二进制文件调用并具有标准化的接口，每一个 IPAM Plugin 都必须要确定容器网络接口的 IP、子网以及网关和路由，并将信息返回给 CNI 插件。最常见的 IPAM Plugin 是 <code>host-local</code>，它从预定义的一组地址池中为容器分配 IP 地址。它将地址池的信息以及分配信息保存在主机的文件系统中，从而确保了同一主机上每个容器的 IP 地址的唯一性。</li>
</ul>
</li>
<li>最后 Kubelet 会将集群内部的 <code>DNS</code> 服务器的 <code>Cluster IP</code> 地址传给 CNI 插件，然后 CNI 插件将它们写到容器的 <code>/etc/resolv.conf</code> 文件中。</li>
</ul>
<p>一旦完成了上面的步骤，CNI 插件就会将操作的结果以 json 的格式返回给 Kubelet。</p>
<h3 id="跨主机容器网络">跨主机容器网络</h3>
<p>到目前为止，我们已经描述了容器如何与宿主机进行通信，但跨主机之间的容器如何通信呢？</p>
<p>通常情况下使用 <code>overlay</code> 网络来进行跨主机容器通信，这是一种动态同步多个主机间路由的方法。 其中最常用的 overlay 网络插件是 <code>flannel</code>，flannel 具体的工作方式可以参考 <a href="https://github.com/coreos/flannel">CoreOS 的文档</a>。</p>
<h3 id="容器启动">容器启动</h3>
<p>所有网络都配置完成后，接下来就开始真正启动业务容器了！</p>
<p>一旦 sanbox 完成初始化并处于 <code>active</code> 状态，Kubelet 就可以开始为其创建容器了。首先<a href="https://github.com/kubernetes/kubernetes/blob/5adfb24f8f25a0d57eb9a7b158db46f9f46f0d80/pkg/kubelet/kuberuntime/kuberuntime_manager.go#L690">启动 PodSpec 中定义的 init 容器</a>，然后再启动业务容器。具体过程如下：</p>
<ol>
<li>首先拉取容器的镜像。如果是私有仓库的镜像，就会利用 PodSpec 中指定的 Secret 来拉取该镜像。</li>
<li>然后通过 CRI 接口创建容器。Kubelet 向 PodSpec 中填充了一个 <code>ContainerConfig</code> 数据结构（在其中定义了命令，镜像，标签，挂载卷，设备，环境变量等待），然后通过 <code>protobufs</code> 发送给 CRI 接口。对于 Docker 来说，它会将这些信息反序列化并填充到自己的配置信息中，然后再发送给 <code>Dockerd</code> 守护进程。在这个过程中，它会将一些元数据标签（例如容器类型，日志路径，dandbox ID 等待）添加到容器中。</li>
<li>接下来会使用 CPU 管理器来约束容器，这是 Kubelet 1.8 中新添加的 alpha 特性，它使用 <code>UpdateContainerResources</code> CRI 方法将容器分配给本节点上的 CPU 资源池。</li>
<li>最后容器开始真正<a href="https://github.com/kubernetes/kubernetes/blob/5f9f4a1c5939436fa320e9bc5973a55d6446e59f/pkg/kubelet/kuberuntime/kuberuntime_container.go#L135">启动</a>。</li>
<li>如果 Pod 中配置了容器生命周期钩子（Hook），容器启动之后就会运行这些 <code>Hook</code>。Hook 的类型包括两种：<code>Exec</code>（执行一段命令） 和 <code>HTTP</code>（发送HTTP请求）。如果 PostStart Hook 启动的时间过长、挂起或者失败，容器将永远不会变成 <code>running</code> 状态。</li>
</ol>
<h2 id="一图看懂过程">一图看懂过程</h2>
<p>链接：<a href="https://hugo-picture.oss-cn-beijing.aliyuncs.com/what-happens-when-k8s.svg">图片地址</a></p>

  </main>
  <div id="disqus-container">
  
    <button id="disqus-button" onclick="showComments()">Show comments</button>
    <div id="disqus-comments">
      
      
      
        <div id="disqus_thread">
        </div>
        <script type="application/javascript">
          function showComments() {
            // Config
var disqus_config = function () {
};
// Build and append comments 
var d = document;
var s = d.createElement('script');
s.async = true;
s.src = '//' + "yanjiemao" + '.disqus.com/embed.js';
s.setAttribute('data-timestamp', + new Date());
(d.head || d.body).appendChild(s);
            // Remove button
var disqusButton = document.getElementById('disqus-button');
disqusButton.parentNode.removeChild(disqusButton); 
// Un-hide comments
var disqusComments = document.getElementById('disqus-comments');
disqusComments.style.display = 'block'; 
          }
        </script>
      
      <noscript>Enable JavaScript to view Disqus comments.</noscript>
    </div>
  
</div>


          
            <footer role="contentinfo">
  <div
  
  >
    <label for="themer">
      dark theme: <input type="checkbox" id="themer" class="vh">
      
      <span aria-hidden="true"></span>
    </label>
  </div>
  
    Made with <a href="https://gohugo.io/">Hugo</a>. Themed by <a href="https://github.com/zwbetz-gh/cupper-hugo-theme">Cupper</a>.
  
</footer>

          
        </div>
      </div>
    </div>
    

<script src="/cloud/js/dom-scripts.js"></script>  

<script src="/cloud/js/prism.js"></script>



<script src="/cloud/js/search.7aef046a0cc8b0c532f1d20087b920459bc868c936bb48a6ae221eceefca2d07.js"></script>

<link rel="stylesheet" href="/cloud/css/search.fe0cd54a21628574bff49d721c827d1bb165ab56b0f22dd55ae78addbe61c309.css"></link>



  <script src="/cloud/js/custom_01.js"></script>

  <script src="/cloud/js/custom_02.js"></script>


    
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.10.1/dist/katex.min.css" integrity="sha384-dbVIfZGuN1Yq7/1Ocstc1lUEm+AT+/rCkibIcC/OmWo5f0EA48Vf8CytHzGrSwbQ" crossorigin="anonymous">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.10.1/dist/katex.min.js" integrity="sha384-2BKqo+exmr9su6dir+qCw08N2ZKRucY4PrGQPPWU1A7FtlCGjmEGFqXCv5nyM5Ij" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.10.1/dist/contrib/auto-render.min.js" integrity="sha384-kWPLUVMOks5AQFrykwIup5lo0m3iMkkHrD0uJ4H5cjeGihAutqP0yW0J6dpFiVkI" crossorigin="anonymous" onload="renderMathInElement(document.body);"></script>

    
  
  <script>
  window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
  ga('create', 'UA-123456789-1', 'auto');
  ga('send', 'pageview');
  </script>
  <script async src='https://www.google-analytics.com/analytics.js'></script>
  

  </body>
</html>
